﻿//===========================[ 7 / 8 / 2011 LVI ]==========================
//				Copyright c Vitaly Lyaschenko <SkyChameleon@gmail.com>
//								Source file
// Purpose: 
//=========================================================================
#include "Kernel.h"

#include "cFileManager.h"
#include "cSoundManager.h"

static const char WINDOW_CLASS_NAME[] = "KernelApplication";
static RECT      g_clipRect;

time_t t;
struct tm *timeInfo;

Kernel::Kernel(void) : 
        m_FileManager(0),
        m_Logger(0),
        m_ParserCfg(0),
        m_Timer(0),
        m_Render(0),
        m_Mouse(0)
{
    m_hWnd = nullptr; 
    m_hInstance = nullptr;

    m_Logger = new cwLogger();
    m_Logger->create( "Log_pgd.txt" );
    time(&t);
    timeInfo = localtime( &t );
    cwLogger::writeINFO( "Log Create : "+std::string( asctime(timeInfo) ) );
    cwLogger::writeINFO( "< Kernel > Instance Create." );
}

Kernel::~Kernel(void)
{
    cwLogger::writeINFO("< Kernel > Instance Destroy.");
    if ( m_Logger )
        SAFE_DELETE( m_Logger );
}

LRESULT CALLBACK Kernel::StaticWndProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
    //http://thetavern.servebeer.com/?p=articles&a=Using+a+window+message+callback+inside+a+class+%28C%252B%252B%29
    Kernel* pParent;
    // Get pointer to window
    if( Msg == WM_NCCREATE )
    {
        pParent = (Kernel*)( (LPCREATESTRUCT)lParam )->lpCreateParams;
        SetWindowLongPtr( hWnd, GWLP_USERDATA, (LONG_PTR)pParent );
    }
    else
    {
        pParent = (Kernel*)GetWindowLongPtr( hWnd, GWLP_USERDATA );
        if( !pParent ) return DefWindowProc( hWnd, Msg, wParam, lParam );
    }
    // Call the non-static window proc
    pParent->m_hWnd = hWnd;
    return pParent->WindowProc( Msg, wParam, lParam );
}

LRESULT Kernel::WindowProc( UINT msg, WPARAM wParam, LPARAM lParam )
{		
    m_Mouse->hMsg( m_hWnd, msg, wParam, lParam );

    //m_KeyEvent->handleMsg(c_hWnd, msg, wParam, lParam);

    switch ( msg )
    {
    case WM_CLOSE:
        QuitLoop();
        break;
    case WM_SIZE:
        {
            m_DisplayCfg->width  = (int)(LOWORD(lParam));
            m_DisplayCfg->height = (int)(HIWORD(lParam));
            m_AbstractApp->Reset();
        } break;
    }	
    return DefWindowProc( m_hWnd, msg, wParam, lParam );
}

bool Kernel::Create()
{
    // берем хэндл из потока
    m_hInstance = (HINSTANCE)GetModuleHandle( nullptr );
    if ( ! m_hInstance )
    {
         cwLogger::writeERROR("ERROR get Module Handle!");
         return false;
    }
    // создаем экз. таймера
    m_Timer = new cTimer;
    if ( !m_Timer )
    {
        cwLogger::writeERROR("ERROR create cTimer!");
        return false;
    }
    // создаем экз. файлового менеджера
    m_FileManager = new cFileManager;
    if ( !m_FileManager )
    {
        cwLogger::writeERROR("ERROR create cFileManager!");
        return false;
    }
    // создаем экз. файлового менеджера
    m_soundManager = new cSoundManager;
    if ( !m_soundManager )
    {
        cwLogger::writeERROR("ERROR create cSoundManager!");
        return false;
    }
    // создаем экземпляр скриптовой системы
    m_Script = new cLuaScript;
    if ( !m_Script )
    {
        cwLogger::writeERROR("ERROR create cLuaScript!");
        return false;
    }
    // инициализтруем Lua VM
    if( !m_Script->init() ) 
    {
         cwLogger::writeERROR("ERROR create cLuaScript!");
         return false;
    }
    // берем параметры из config файла
    m_ParserCfg = new cParserCfg;
    if ( !m_ParserCfg->init( "config.lua" ) )
    {
        cwLogger::writeERROR("Wrong get Config!");
        return false;
    }
   
    // устанавливаем параметры рендеринга из конф. файла
    m_DisplayCfg = m_ParserCfg->getRenderCnfg();

    // создаем экз. рендера
    m_Render = new cRenderDevice;
    if ( !m_Render )
    {
        cwLogger::writeERROR("ERROR create cRenderDevice!");
        return false;
    }

    m_Mouse = new cMouse;

    if ( !CreateRenderWindow() )
    {
         cwLogger::writeERROR("ERROR create Render Window!");
         return false;
    }

    setWindowTitle( m_ParserCfg->nameApp.c_str() );

    if ( !m_Render->init( m_hWnd, m_DisplayCfg ) )
    {
         cwLogger::writeERROR("ERROR create Render Device");
         return false;
    }

    cwLogger::writeINFO("< Kernel > Initialize Success!");
    return true;
}

void Kernel::Destroy()
{	
    // восстановим разрешение экрана
    if ( m_DisplayCfg->fullScreen )
        ChangeDisplaySettings( nullptr, CDS_RESET );

    if ( m_hWnd )
        CloseRenderWindow();

    // удаляем окно
    if ( m_hWnd )
        DestroyWindow( m_hWnd );
    // удаляем класс окна
    if ( m_hInstance )
        UnregisterClass( WINDOW_CLASS_NAME, m_hInstance );

    if ( m_Timer  )      SAFE_DELETE( m_Timer );
    if ( m_Script )      SAFE_DELETE( m_Script );
    if ( m_Render )	     SAFE_DELETE( m_Render );
    if ( m_Mouse  )      SAFE_DELETE( m_Mouse );
    if ( m_soundManager) SAFE_DELETE( m_soundManager );
    if ( m_FileManager ) SAFE_DELETE( m_FileManager );
    if ( m_ParserCfg )   SAFE_DELETE( m_ParserCfg );

    m_DisplayCfg = nullptr;
}

void Kernel::QuitLoop()
{
    PostQuitMessage( NULL );
}

int Kernel::ExecLoop()
{
    cTimer::init();

    this->m_AbstractApp->Init();

    MSG msg;
    ZeroMemory( &msg, sizeof( MSG ) );

    while( msg.message != WM_QUIT ) // если не сообщение о выходе
    {
        // проверяем, есть ли сообщения в очереди
        if ( PeekMessage( &msg, nullptr, 0, 0, PM_REMOVE ) )
        {
            // обработка сообщений
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        } else
        {
            cTimer::update();

            m_Mouse->update();

            m_AbstractApp->Update();

            m_AbstractApp->Render();

            Sleep(5);
        }
    }

    m_AbstractApp->ShutDown();

    return ( int )msg.wParam;
}

bool Kernel::CreateRenderWindow()
{
    WNDCLASSEX	wcx;
    DWORD       style, exStyle;
    RECT		rect;
    int			x=0, y=0;

    std::stringstream strLastError;

    memset( &wcx, 0, sizeof( wcx ) );

    wcx.cbSize        = sizeof( wcx );
    wcx.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wcx.lpfnWndProc   = StaticWndProc;
    wcx.hInstance     = m_hInstance;
    wcx.lpszClassName = WINDOW_CLASS_NAME;
    wcx.hIcon         = LoadIcon( nullptr, IDI_APPLICATION );
    wcx.hCursor       = LoadCursor( nullptr, IDC_ARROW );

    if ( !RegisterClassEx( &wcx ) )
    {
        strLastError << GetLastError();
        cwLogger::writeERROR( "RegisterClassEx fail. Last err: " + strLastError.str() );
        return false;
    }
    
    // стили окна
    style   = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
    exStyle = WS_EX_APPWINDOW;

    // выровнять окно по центру экрана
    x = ( GetSystemMetrics( SM_CXSCREEN ) - m_DisplayCfg->width ) / 2;
    y = ( GetSystemMetrics( SM_CYSCREEN ) - m_DisplayCfg->height ) / 2;

    rect.left   = x;
    rect.right  = x + m_DisplayCfg->width;
    rect.top    = y;
    rect.bottom = y + m_DisplayCfg->height;

    // подгоним размер окна под стили
    AdjustWindowRectEx ( &rect, style, FALSE, exStyle );

    m_hWnd = CreateWindowEx( NULL, 
                            WINDOW_CLASS_NAME,
                            "SC",
                            WS_OVERLAPPEDWINDOW, 
                            rect.left, 
                            rect.top, 
                            rect.right - rect.left, 
                            rect.bottom - rect.top, 
                            NULL, 
                            NULL,  
                            m_hInstance, 
                            this );
    if( m_hWnd == NULL)
    {
        strLastError << GetLastError();
        cwLogger::writeERROR( "Unable to create the window. Last err:" + strLastError.str() );
        return false;
    }

    ShowWindow( m_hWnd, SW_SHOW );

    UpdateWindow( m_hWnd );

    cwLogger::writeINFO( "< Kernel > Render Window Create." );

    return true;
}
///////////////////////////////////////////////////////////////////////////
// установить параметры окна
void Kernel::setWindowSetting( int width, int height, bool fullScreen )
{
    ASSERT( width  > 0 );
    ASSERT( height > 0 );

    RECT    rect;
    DWORD   style, exStyle;
    DEVMODE devMode;
    LONG    result;
    int     x, y;

    // если вкрнулись из полноэкранного режима
    if ( m_DisplayCfg->fullScreen && !fullScreen )
        ChangeDisplaySettings( nullptr, CDS_RESET );

    m_DisplayCfg->fullScreen = fullScreen;

    // если необходим полноэкранный режим
    if ( m_DisplayCfg->fullScreen )
    {
        memset( &devMode, 0, sizeof( devMode ) );

        devMode.dmSize       = sizeof( devMode );
        devMode.dmPelsWidth  = width;
        devMode.dmPelsHeight = height;
        devMode.dmBitsPerPel = GetDeviceCaps( GetDC( m_hWnd ), BITSPIXEL );
        devMode.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

        // установить полноэкранный режим
        result = ChangeDisplaySettings( &devMode, CDS_FULLSCREEN );
        if ( result != DISP_CHANGE_SUCCESSFUL )
           m_DisplayCfg->fullScreen = false;
    }
    // если был запрошен полноэкранный режим и его удалось установить
    if ( m_DisplayCfg->fullScreen )
    {
        style   = WS_POPUP;
        exStyle = WS_EX_APPWINDOW | WS_EX_TOPMOST;
        x = y = 0;
    }else 
        // если полноэкранный режим не нужен, или его не удалось установить
    {
        //http://msdn.microsoft.com/en-us/library/ms632600(VS.85).aspx
        style   = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
        exStyle = WS_EX_APPWINDOW;

        // выровняем окно по центру экрана
        x = ( GetSystemMetrics(SM_CXSCREEN) - width )  / 2;
        y = ( GetSystemMetrics(SM_CYSCREEN) - height ) / 2;
    }

    rect.left   = x;
    rect.right  = x + width;
    rect.top    = y;
    rect.bottom = y + height;

    // регулировка под стили
    AdjustWindowRectEx( &rect, style, FALSE, exStyle );

    // установка стилей окна
    SetWindowLong( m_hWnd, GWL_STYLE,   style );
    SetWindowLong( m_hWnd, GWL_EXSTYLE, exStyle );

    // обновть позицию окна
    SetWindowPos( m_hWnd, HWND_TOP, rect.left, rect.top,
        rect.right - rect.left, rect.bottom - rect.top,
        SWP_FRAMECHANGED );

    SetCursorPos(x + width / 2, y + height / 2);

    ShowWindow( m_hWnd, SW_SHOW );
    SetForegroundWindow( m_hWnd );
    SetFocus( m_hWnd );
    UpdateWindow( m_hWnd );

    // получить размеры окна
    GetClientRect( m_hWnd, &rect );
    m_DisplayCfg->width  = rect.right - rect.left;
    m_DisplayCfg->height = rect.bottom - rect.top;

    m_DisplayCfg->Xcenter = GetSystemMetrics( SM_CXSCREEN );
    m_DisplayCfg->Ycenter = GetSystemMetrics( SM_CYSCREEN );

}

//////////////////////////////////////////////////////////////////////////
void Kernel::AdjustDisplaySettings()
{
    if( m_DisplayCfg->fullScreen )
    {
        DEVMODE devMode;
        memset( &devMode, 0, sizeof( devMode ) );
        EnumDisplaySettings( nullptr, ENUM_CURRENT_SETTINGS, &devMode );
        devMode.dmSize	= sizeof(DEVMODE);
        devMode.dmPelsWidth	= m_DisplayCfg->width;
        devMode.dmPelsHeight = m_DisplayCfg->height;
        devMode.dmBitsPerPel = 32;
        devMode.dmFields     = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
        LONG result = ChangeDisplaySettings( &devMode, CDS_FULLSCREEN );
        // если не удалось установить полноэкранный режим
        if ( result != DISP_CHANGE_SUCCESSFUL )
        {
            cwLogger::writeERROR( "Fail ChangeDisplaySettings !" );
            m_DisplayCfg->fullScreen = false;
        }
    }
}

void Kernel::CloseRenderWindow()
{
    CloseWindow( m_hWnd );
}

void Kernel::setWindowTitle( const char *title )
{
    SetWindowText( m_hWnd, title );
}
